一、ACID:事务的4个特性
- 原子性(atomicity):在一个事务中,操作要么成功,要么失败。如果事务中有一个命令失败,必须回滚操作,回到事务执行之前的状态。
- 一致性(consistency):事务在完成时,必须使所有的数据都保持一致状态。
- 隔离性(isolation):事务在查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务是不会查看中间状态的。
- 持久性(durability):事务完成之后,它对于系统的影响是永久性的。
二、隔离级别(ACID中的I)
InnoDB
提供四种隔离级别
- READ UNCOMMITTED: 读未提交
- READ COMMITTED: 读已提交
- REPEATABLE READ: 重复读
- SERIALIZABLE: 串行化
不一致情况: 脏读:一个事务读取了另一个未提交事务写入的数据。 不可重复读:指一个事务重新读取前面读取过的数据时,发现该数据已经被另一个已提交事务修改了。 幻读:一个事务开始后,需要根据数据库中现有的数据做一些更新,于是重新执行一个查询,返回一套符合查询条件的行,这是发现这些行因为其他最近提交的事务而发生了改变,导致现有的事务如果再进行下去就可能会在逻辑上出现一些错误。(幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。)
事务隔离级别的行为
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不可能 | 可能 | 可能 |
重复读 | 不可能 | 不可能 | 可能 |
可串行化 | 不可能 | 不可能 | 不可能 |
模拟发生幻读:
事务1
事务2
事务1:检查表中是否有id=10
的记录,没有则插入,这是正常逻辑。 事务2: 干拢了事务1的正常执行。
用户可以通过SET TRANSACTION
命令改变隔离级别,或通过--transaction-isolation
配置server级别的隔离性。 MySQL
通过使用不同的锁策略实现不同的隔离级别,默认隔离级别是REPEATABLE READ
,以提供更高级别的一致性。你也可以指定隔离级别为READ COMMITTED
甚至READ UNCOMMITTED
一致性要求没那么高的场景(减少锁以提高性能)。SERIALIZABLE
提供比REPEATABLE
更严格的 规则,一般会用于一些特殊的场景,比如XA事务和用于调试并发或死锁问题。
REPEATABLE READ
这是MySQL的默认隔离级别。在同一事务中的读取的数据是一致的,在第一次读时会建立一个快照,同一事务中读取到的都是第一个快照中的值。也就是说在同一个事务中,SELECT(noblocking)所获取到的数据都是一致的。对于REPEATABLE READ
隔离级别总是读取事务开始时的行数据。
READ COMMITTED 读已提交
每一次读都会建立一个新的快照并从中读取数据,它总是读取行的最新版本,如果行被锁定,则读取该行版本的最新一个快照。 对于READ COMMITTED
只支持 row-based的binlog。 其它影响: - 对于UPDATE
,DELETE
操作,InnoDB
只会锁住需要需要更新或删除的行。对于不匹配的行,在执行完WHERE
语句后锁会释放。这极大的降低了死锁的概率,但还是有可能发生。 - 对于UPDATE
操作,如果行已经被加锁,InnoDB``表现为semi-consistent读,将返回最近提交的版本,
MySQL再决定是否符合
WHERE条件。如果匹配,
MySQL```会重新读取这行,然后获取锁或者等待锁。
READ UNCOMMITTED
SELECT
表现为nonlocking fashion,但是可能会读取到旧版本的数据,所以这种隔离级别不保持一致性读,也称为脏读。
SERIALIZABLE
SERIALIZABLE
和REPEATABLE READ
隔离级别类似,当autocommit=false
情况下,所有SELECT
语句会被转换为SELECT ... LOCK IN SHARE MODE
。
示例: MySQL
默认隔离级别为: 在REPEATABLE READ
隔离级别情况下,开启两个session,先左边的窗口开启一个事务读取表a中的数据,然后右边的窗口再开启个事务对表a数据进行修改,右边的事务提交后,在左边的事务再次查看表a中的数据。发现两次得到的数据是一致性的,因为对于REPEATABLE READ
隔离级别总是读取事务开始时的行数据。 在READ COMMITTED
隔离级别重复上述的操作,发现在右边的事务提交后再查看表a的数据,此时表a的数据已更新。因为在这个隔离级别下,它总是读取行的最新版本,如果行被锁定,则读取该行版本的最新一个快照。